﻿// Sequin.Sequences.Mining.GraphWriter
//
// (c) 2011 Arthur Pitman
//
// Part of the Sequin Library
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License (Version 2.1) as published by the Free Software Foundation.
// 
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
// 
// You should have received a copy of the GNU Lesser General Public
// License (Version 2.1) along with this library; if not, write to the 
// Free Software Foundation, Inc., 
// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
// or see <http://www.gnu.org/licenses/>.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace Sequin.Sequences.Mining
{
	/// <summary>
	/// Writes a projection tree graph (GraphViz) by implementing the <see cref="ISequenceMinerObserver"/> interface
	/// </summary>
	public class GraphWriter : ISequenceMinerObserver
	{
		/// <summary>
		/// The <see cref="StreamWriter"/> used to write the graph
		/// </summary>
		protected StreamWriter streamWriter;

		/// <summary>
		/// The <see cref="ISequenceFormatter"/> used for formatting
		/// </summary>
		protected ISequenceFormatter sequenceFormatter;

		/// <summary>
		/// The associated <see cref="ISymbolMapper"/>
		/// </summary>
		protected ISymbolMapper symbolMapper;

		/// <summary>
		/// Cache of <see cref="GraphNode"/> instances in the graph
		/// </summary>
		protected Dictionary<long, GraphNode> nodes = new Dictionary<long, GraphNode>();

		/// <summary>
		/// Internal class used to represent a graph node
		/// </summary>
		protected class GraphNode
		{
			/// <summary>
			/// Possible states for a node
			/// </summary>
			public enum GraphNodeState
			{
				/// <summary>
				/// The node has been generated
				/// </summary>
				Generated,

				/// <summary>
				/// The node has been expanded (not pruned)
				/// </summary>
				Expanded,

				/// <summary>
				/// The node resulted in a pattern being emitted
				/// </summary>
				Emitted
			}
			
			/// <summary>
			/// The id of the <see cref="GraphNode"/>
			/// </summary>
			public long Id { get; protected set; }

			/// <summary>
			/// The string of the <see cref="Sequence"/> of the <see cref="GraphNode"/>
			/// </summary>
			public string SequenceString { get; protected set; }

			/// <summary>
			/// The support count of the <see cref="GraphNode"/>
			/// </summary>
			public int SupportCount { get; protected set; }

			/// <summary>
			/// The current <see cref="GraphNodeState"/> of the <see cref="GraphNode"/>
			/// </summary>
			public GraphNodeState State { get; set; }

			/// <summary>
			/// Initializes a new instance of the <see cref="GraphNode"/> class
			/// </summary>
			/// <param name="id">The id of the <see cref="GraphNode"/></param>
			/// <param name="sequenceString">The string of the <see cref="Sequence"/> of the <see cref="GraphNode"/></param>
			/// <param name="supportCount">The support count of the <see cref="GraphNode"/></param>
			public GraphNode(long id, string sequenceString, int supportCount)
			{
				Id = id;
				SequenceString = sequenceString;
				SupportCount = supportCount;
				State = GraphNodeState.Generated;
			}
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="GraphWriter"/> class
		/// </summary>
		/// <param name="graphFilename">The filename to write the graph to</param>
		/// <param name="sequenceFormatter">The associated <see cref="ISequenceFormatter"/></param>
		/// <param name="symbolMapper">The associated <see cref="ISymbolMapper"/></param>
		public GraphWriter(string graphFilename, ISequenceFormatter sequenceFormatter, ISymbolMapper symbolMapper)
		{
			streamWriter = File.CreateText(graphFilename);
			this.symbolMapper = symbolMapper;
			this.sequenceFormatter = sequenceFormatter;
		}

		/// <summary>
		/// Called to signal the start of the mining process
		/// </summary>
		public void OnStartMining()
		{			
			streamWriter.WriteLine("graph test {");
		}

		/// <summary>
		/// Called to signal that a <see cref="Projection"/> has been expanded
		/// </summary>
		/// <param name="projection">The associated <see cref="Projection"/></param>
		public void OnExpandProjection(Projection projection)
		{
			lock (nodes)
			{
				nodes[projection.Id].State = GraphNode.GraphNodeState.Expanded;
			}
		}

		/// <summary>
		/// Called to signal that a <see cref="Projection"/> has been generated
		/// </summary>
		/// <param name="projection">The associated <see cref="Projection"/></param>
		public void OnGenerateProjection(Projection projection)
		{					
			lock (nodes)
			{
				if (projection.Parent != null)
					streamWriter.WriteLine("N_{0} -- N_{1};", projection.Parent.Id, projection.Id);
				nodes.Add(projection.Id, new GraphNode(projection.Id, sequenceFormatter.SequenceToString(projection.ProjectingSequence, symbolMapper), projection.ProjectedDb.Count));
			}
		}

		/// <summary>
		/// Called to signal that a pattern has been emitted
		/// </summary>
		/// <param name="projection">The associated <see cref="Projection"/></param>
		public void OnEmitPattern(Projection projection)
		{
			lock (nodes)
			{
				nodes[projection.Id].State = GraphNode.GraphNodeState.Emitted;
			}
		}

		/// <summary>
		/// Called to signal the end of a mining process
		/// </summary>
		public void OnFinishMining()
		{		
			// emit all nodes
			lock (nodes)
			{
				foreach (var node in nodes.Values)
				{
					string extraAttributes = "";
					if (node.State == GraphNode.GraphNodeState.Generated)
						extraAttributes = ", style=dotted";
					else if (node.State == GraphNode.GraphNodeState.Emitted)
						extraAttributes = ", style=bold";
					streamWriter.WriteLine("N_{0} [label=\"{1}: {2}\"{3}];",
						node.Id,
						node.SequenceString,
						node.SupportCount,
						extraAttributes
					);
				}
			}

			// finish and close the graph
			streamWriter.WriteLine("}");
			streamWriter.Close();
		}
	}
}
